﻿/*
* This file is part of WinMergeFS.
*
* WinMergeFS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.

* WinMergeFS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WinMergeFS.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using WinMergeFS.Common;
using RealtimeTrace;

namespace WinMergeFS.API
{
    /// <summary>
    /// Manages RootSelection plugins.
    /// </summary>
    internal class RootSelectManager : Itraceable
    {

        #region RealtimeTrace

        /// <summary>
        /// Use this method to initialize the RealTimeTrace.
        /// This method should be called within the class constructor(s) 
        /// if you want to override the default RealtimeTrace configuration.
        /// </summary>
        private void InitializeRealTimeTrace()
        {
            this.RealTimeTraceProvider = DataProviderType.LogFile;
            this.PreferredDumpType = DumpType.XML;
        }

        private DataProviderType _RealTimeTraceProvider = DataProviderType.LogFile;
        /// <summary>
        /// Gets or Sets the Data Store provider for the RealTimeTrace.
        /// The default Data Storage provider is a raw text file.
        /// For Windows application, this raw text file is stored at the same level of the running executable.
        /// For ASP.NET application, this raw text file is stored at the same level of the aspx page.
        /// For UI Components, this raw text file is stored in the "My Documents" user folder.
        /// By default the name of the log file is *RealtimeTrace.log*
        /// </summary>
        public DataProviderType RealTimeTraceProvider
        {
            get
            {
                return this._RealTimeTraceProvider;
            }
            set
            {
                this._RealTimeTraceProvider = value;
            }
        }

        private DumpType _PreferredDumpType = DumpType.XML;
        /// <summary>
        /// Gets or Sets the type of dump for the current class.
        /// </summary>
        public DumpType PreferredDumpType
        {
            get
            {
                return this._PreferredDumpType;
            }
            set
            {
                this._PreferredDumpType = value;
            }
        }

        private bool _HasErrors = false;
        /// <summary>
        /// Checks if an error has occured within this class instance.
        /// The Itraceable implementer should only read this property.
        /// This property is set only by the RealtimeTrace layer.
        /// </summary>
        public bool HasErrors
        {
            get { return this._HasErrors; }
            set { this._HasErrors = value; }
        }

        private System.String _LastError = System.String.Empty;
        /// <summary>
        /// Gets the last known error from the last call of a public/private member within this class instance.
        /// The Itraceable implementer should only read this property.
        /// This property is set only by the RealtimeTrace layer.
        /// </summary>
        public string LastError
        {
            get { return this._LastError; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._LastError = value;
            }
        }

        private System.String _Errors = System.String.Empty;
        /// <summary>
        /// Gets all errors from the last call of a public member within this class instance.
        /// The Itraceable implementer should only read this property.
        /// This property is set only by the RealtimeTrace layer.
        /// </summary>
        public string Errors
        {
            get { return this._Errors; }
            set
            {
                if (value == null)
                {
                    value = System.String.Empty;
                }
                this._Errors = value;
            }
        }

        #endregion

        private string Plugin_Dir = Environment.CurrentDirectory + "\\Plugins\\";

        private Profile _profile;
        private LoggerEventHandler _logger;
        private RootSelectHelper _rootselecthelper;
        private Dictionary<string, RootSelectPluginDelegate> _fileratinghandler = new Dictionary<string, RootSelectPluginDelegate>();
        private Dictionary<string, RootSelectPluginDelegate> _dirratinghandler = new Dictionary<string, RootSelectPluginDelegate>();
        private List<IRootSelectPlugin> _plugins = new List<IRootSelectPlugin>();

        public RootSelectManager(Profile profile, LoggerEventHandler logger)
        {
            try
            {
                _profile = profile;
                _logger = logger;
                _rootselecthelper = new RootSelectHelper(_profile);
                InitializeInbuildPlugins();
                InitializeExternPlugins();
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, profile, logger); throw; }
        }

        public List<IRootSelectPlugin> LoadedPlugins
        {
            get
            {
                return _plugins;
            }
            set
            {
            }
        }

        private void InitializeInbuildPlugins()
        {
            try
            {
                IRootSelectPlugin plugin;
                plugin = new RootSelectInbuildPlugin();

                plugin.PluginRegister(_profile, _logger, ref _fileratinghandler, ref _dirratinghandler);

                _plugins.Add(plugin);
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex); }
        }

        private void InitializeExternPlugins()
        {
            try
            {
                Assembly asm;
                IRootSelectPlugin plugin;

                _logger("InitializeExternPlugins: Scanning for plugins in " + Plugin_Dir, LogLevel.info);
                if (!Directory.Exists(Plugin_Dir)) { _logger("InitializeExternPlugins: No external plugins found.", LogLevel.info); return; }
                string[] files = Directory.GetFiles(Environment.CurrentDirectory + "\\plugins\\", "*.dll");
                if (files.Length == 0) { _logger("InitializeExternPlugins: No external plugins found.", LogLevel.info); return; }
                foreach (string file in files)
                {
                    _logger("InitializeExternPlugins: Loading " + file, LogLevel.debug);
                    asm = Assembly.LoadFrom(file);

                    foreach (Type typ in asm.GetTypes())
                    {
                        _logger("InitializeExternPlugins: Trying to load class " + typ.ToString(), LogLevel.debug);
                        plugin = (IRootSelectPlugin)Activator.CreateInstance(typ);
                        plugin.PluginRegister(_profile, _logger, ref _fileratinghandler, ref _dirratinghandler);
                        _plugins.Add(plugin);
                        plugin = null;
                    }
                }
            }
            catch (Exception ex)
            {
                TraceFactory.InsertTrace(this, ex);
                _logger("InitializeExternPlugins: " + ex.ToString() + " - " + ex.Message + " --- " + ex.StackTrace, LogLevel.error);
            }
        }

        /// <summary>
        /// Returns root id for file create operation.
        /// </summary>
        public int GetRootForCreateOP(string filename)
        {
            return GetRootForCreateOP(filename, false);
        }

        public int GetRootForCreateOP(string filename, bool isDirectory)
        {
            try
            {
                _logger("Plugins: Evaluating rootrating for file " + filename, LogLevel.verbose);
                List<int> _rootrating = new List<int>();

                for (int i = 0; i < _profile.Roots.Count; i++)
                {
                    if ((_profile.Roots[i].ReadOnly) || (!Directory.Exists(_profile.Roots[i].RootPath))) { _rootrating.Add(-1); }
                    else _rootrating.Add(0);
                }

                if (!isDirectory) // File
                {

                    foreach (ProfilePluginEntry entry in _profile.Plugins.Values) //TODO: Sort by position
                    {
                        if (_fileratinghandler.ContainsKey(entry.PluginID))
                        {
                            _fileratinghandler[entry.PluginID](_profile, ref _rootrating, filename);
                        }
                        else { _logger("RootSelectManager: File-Plugin not found " + entry.PluginID, LogLevel.error); }
                        if (_rootrating.Contains(-2)) { break; }
                    }

                    return CalcBestRoot(_rootrating);
                }

                else // Directory
                {
                    foreach (ProfilePluginEntry entry in _profile.Plugins.Values) //TODO: Sort by position
                    {
                        if (_dirratinghandler.ContainsKey(entry.PluginID))
                        {
                            _dirratinghandler[entry.PluginID](_profile, ref _rootrating, filename);
                        }
                        else { _logger("RootSelectManager: Dir-Plugin not found " + entry.PluginID, LogLevel.error); }
                        if (_rootrating.Contains(-2)) { break; }
                    }

                    return CalcBestRoot(_rootrating);
                }
            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, filename, isDirectory); return -1; }
        }

        private int CalcBestRoot(List<int> _rootrating)
        {
            try
            {

                // a plugin can force a root by giving it a rating of -2
                if (_rootrating.Contains(-2))
                {
                    return _rootrating.IndexOf(-2);
                }

                // return -1 if there is no suitable slave (max <= 0), else return the index of the slave with the best rating
                int max = _rootrating.Max();
                if (max > 0) return _rootrating.IndexOf(_rootrating.Max());
                else return -1;

            }
            catch (Exception ex)
            { TraceFactory.InsertTrace(this, ex, _rootrating); return -1; }
        }

        public static List<IRootSelectPlugin> GetAvailablePlugins()
        {
            throw new System.NotImplementedException();
        }

    }
}